package net.sf.ivyide.ivy;

import com.intellij.openapi.roots.DependencyScope;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.JDOMExternalizable;
import com.intellij.openapi.util.WriteExternalException;
import net.sf.ivyide.ModuleId;
import org.apache.commons.lang.StringUtils;
import org.jdom.Attribute;
import org.jdom.Element;

import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * User: janssk1
 * Date: 23-okt-2010
 * Time: 15:47:32
 */
public class Configurations implements JDOMExternalizable {

    public class Configuration implements JDOMExternalizable {
        private String[] conf = new String[0];
        private final String key;

        public Configuration(String key, String... conf) {
            this.key = key + "Configurations";
            setConf(conf);
        }

        public void setConf(String... confs) {
            this.conf = confs;
        }

        public String[] getConf() {
            return conf;
        }

        public String getAsString() {
            return StringUtils.join(conf, ",");
        }

        public void setAsString(String s) {
            if (s.trim().length() == 0) {
                setConf();
            } else {
                setConf(s.split(","));
            }
        }

        public void readExternal(Element element) throws InvalidDataException {
            Attribute attribute = element.getAttribute(key);
            if (attribute != null) {
                setAsString(attribute.getValue());
            }
        }

        public void writeExternal(Element element) throws WriteExternalException {
            element.setAttribute(key, getAsString());
        }
    }

    private final Configuration compile = new Configuration("compile", "default", "compile");
    private final Configuration runtime = new Configuration("runtime", "runtime");
    private final Configuration test = new Configuration("test", "test");
    private final Configuration provided = new Configuration("provided");

    private final Configuration[] allConfigurations = new Configuration[]{compile, runtime, test, provided};

    public Configuration getCompile() {
        return compile;
    }

    public Configurations setCompile(String... compile) {
        this.compile.setConf(compile);
        return this;
    }

    public Configuration getRuntime() {
        return runtime;
    }

    public Configurations setRuntime(String... runtime) {
        this.runtime.setConf(runtime);
        return this;
    }

    public Configuration getProvided() {
        return provided;
    }

    public Configurations setProvided(String... provided) {
        this.provided.setConf(provided);
        return this;
    }

    public Configuration getTest() {
        return test;
    }

    public Configurations setTest(String... test) {
        this.test.setConf(test);
        return this;
    }

    public String[] getAll() {
        Set<String> res = new LinkedHashSet<String>();
        for (Configuration conf : allConfigurations) {
            res.addAll(Arrays.asList(conf.getConf()));
        }
        return res.toArray(new String[res.size()]);
    }

    public DependencyScope getDependencyScope(ModuleId dependencyId, String... configurations) {
        boolean compile = false;
        boolean runtime = false;
        boolean test = false;
        boolean provided = false;
        for (String configuration : configurations) {
            compile = compile || matchConfiguration(this.compile, configuration);
            runtime = runtime || matchConfiguration(this.runtime, configuration);
            provided = provided || matchConfiguration(this.provided, configuration);
            test = test || matchConfiguration(this.test, configuration);
        }
        if (provided) {
            return DependencyScope.PROVIDED;
        } else if (compile) {
            return DependencyScope.COMPILE;
        } else if (test) {
            if (runtime) {
                //both test && runtime. Problem..
                //should we fallback to compile ??
                return DependencyScope.TEST;
                // throw new IllegalStateException("Dependency " + dependencyId + " required by both runtime & test configurations: " + Arrays.toString(configurations));
            } else {
                return DependencyScope.TEST;
            }
        } else {
            if (runtime) {
                return DependencyScope.RUNTIME;
            } else {
                throw new RuntimeException("Dependency " + dependencyId + " belongs to unexpected configuration: " + Arrays.toString(configurations));
            }
        }
    }

    private boolean matchConfiguration(Configuration configuredConfigurations, String configuration) {
        return Arrays.asList(configuredConfigurations.getConf()).contains(configuration);
    }

    public void readExternal(Element element) throws InvalidDataException {
        for (Configuration allConfiguration : allConfigurations) {
            allConfiguration.readExternal(element);
        }
    }

    public void writeExternal(Element element) throws WriteExternalException {
        for (Configuration allConfiguration : allConfigurations) {
            allConfiguration.writeExternal(element);
        }
    }
}
